﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Pahan.McuFusion.Common;
using Pahan.McuFusion.Files;
using Pahan.McuFusion.UI.Events;
using Pahan.McuFusion.UI.Interfaces;
using Pahan.McuFusion.UI.RecentFiles;
using Pahan.McuFusion.UI.Scripting;
using Pahan.McuFusion.UI.Views;
using Pahan.McuFusion.UI.Workspaces;

namespace Pahan.McuFusion.UI.Presenters
{
    public class MainViewPresenter : IDisposable
    {
        public MainViewPresenter(IMainView view, String[] arguments)
        {
            this.view = view;
            this.view.NewWorkspaceRequested += OnNewWorkspaceRequested;
            this.view.OpenWorkspaceRequested += OnOpenWorkspaceRequested;
            this.view.SaveWorkspaceRequested += OnSaveWorkspaceRequested;
            this.view.SaveWorkspaceAsRequested += OnSaveWorkspaceAsRequested;
            this.view.OpenFileRequested += OnOpenFileRequested;
            this.view.SaveFileRequested += OnSaveFileRequested;
            this.view.ReadMemoryRequested += OnReadMemoryRequested;
            this.view.EraseMemoryRequested += OnEraseMemoryRequested;
            this.view.WriteMemoryRequested += OnWriteMemoryRequested;
            this.view.Loaded += OnLoaded;
            this.view.Closing += OnClosing;

            this.view.RefreshRecentFilesList(RecentFilesList.Default.Files);

            this.applicationObject = new ApplicationObject();
            this.applicationObject.NewWorkspaceRequested += OnNewWorkspaceRequested;
            this.applicationObject.OpenWorkspaceRequested += OnOpenWorkspaceRequested;
            this.applicationObject.SaveWorkspaceRequested += OnSaveWorkspaceRequested;
            this.applicationObject.SaveWorkspaceAsRequested += OnSaveWorkspaceAsRequested;
            this.applicationObject.OpenFileRequested += OnOpenFileRequested;
            this.applicationObject.SaveFileRequested += OnSaveFileRequested;
            this.applicationObject.ReadMemoryRequested += OnReadMemoryRequested;
            this.applicationObject.EraseMemoryRequested += OnEraseMemoryRequested;
            this.applicationObject.WriteMemoryRequested += OnWriteMemoryRequested;
            this.applicationObject.ExitRequested += OnExitRequested;
            this.applicationObject.GetCurrentWorkspaceRequested += OnGetCurrentWorkspaceRequested;

            this.arguments = arguments ?? new String[0];
        }

        #region Event Handlers

        private void OnNewWorkspaceRequested(object sender, EventArgs e)
        {
            var wizardView = new WizardView();
            var wizardPres = new NewWorkspaceWizardPresenter(wizardView);
            if (wizardPres.ShowWizard())
            {
                workspace = wizardPres.Workspace;
                workspace.Initialize();
                view.ShowWorkspace(workspace);
            }
        }

        private void OnOpenWorkspaceRequested(object sender, OpenWorkspaceEventArgs e)
        {
            OpenWorkspace(e.File);
        }

        private void OnSaveWorkspaceRequested(object sender, EventArgs e)
        {
            SaveWorkspace();
        }

        private void OnSaveWorkspaceAsRequested(object sender, EventArgs e)
        {
            SaveWorkspaceAs();
        }

        private void OnOpenFileRequested(object sender, MemoryOperationEventArgs e)
        {
            FileInfo file = view.GetOpenFileName(FileFactory.GetReadableFileTypes());
            if (file != null)
            {
                using (var reader = FileFactory.CreateReader(file, e.MemoryClass.EmptyValue))
                {
                    var data = reader.ReadAll();
                    view.ShowMemory(data, e.MemoryClass);
                }
            }
        }

        private void OnSaveFileRequested(object sender, MemoryOperationEventArgs e)
        {
            FileInfo file = view.GetSaveFileName(FileFactory.GetWritableFileTypes());
            if (file != null)
            {
                var data = view.GetMemory(e.MemoryClass);
                if (data != null)
                {
                    using (var writer = FileFactory.CreateWriter(file, e.MemoryClass.EmptyValue))
                    {
                        writer.WriteAll(data);
                    }
                }
            }
        }

        private void OnReadMemoryRequested(object sender, MemoryOperationEventArgs e)
        {
            if (workspace == null || workspace.Programmer == null) return;

            try
            {
                workspace.Programmer.Open();
                workspace.Programmer.ProgressChanged += OnProgrammerProgressChanged;
                workspace.Programmer.AsyncCompleted += OnReadMemoryCompleted;

                progressView = new ProgressView();
                progressView.Title = "MCU Fusion";
                progressView.Cancelled += OnCancel;
                progressView.Text = "Reading memory...";
                progressView.UpdateProgress(0);

                stream = new MemoryStream();
                memoryClass = e.MemoryClass;
                workspace.Programmer.ReadMemoryAsync(stream, e.MemoryClass);
                progressView.ShowDialog();
            }
            catch (Exception ex)
            {
                workspace.Programmer.Close();
                MessageBox.Show(ex.Message);
            }
        }

        private void OnReadMemoryCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }

            workspace.Programmer.ProgressChanged -= OnProgrammerProgressChanged;
            workspace.Programmer.AsyncCompleted -= OnReadMemoryCompleted;

            workspace.Programmer.Close();
            progressView.Close();
            progressView.Dispose();
            progressView = null;
            view.ShowMemory(stream.ToArray(), memoryClass);
            stream.Dispose();
            stream = null;
        }

        private void OnEraseMemoryRequested(object sender, MemoryOperationEventArgs e)
        {
            if (workspace == null || workspace.Programmer == null) return;

            try
            {
                workspace.Programmer.Open();
                workspace.Programmer.ProgressChanged += OnProgrammerProgressChanged;
                workspace.Programmer.AsyncCompleted += OnEraseMemoryCompleted;

                progressView = new ProgressView();
                progressView.Title = "MCU Fusion";
                progressView.Cancelled += OnCancel;
                progressView.Text = "Erasing memory...";
                progressView.UpdateProgress(0);

                stream = new MemoryStream();
                workspace.Programmer.EraseMemoryAsync(e.MemoryClass);
                progressView.ShowDialog();
            }
            catch (Exception ex)
            {
                workspace.Programmer.Close();
                MessageBox.Show(ex.Message);
            }
        }

        private void OnEraseMemoryCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            
            workspace.Programmer.ProgressChanged -= OnProgrammerProgressChanged;
            workspace.Programmer.AsyncCompleted -= OnEraseMemoryCompleted;
            
            workspace.Programmer.Close();
            progressView.Close();
            stream.Dispose();
            stream = null;
        }

        private void OnWriteMemoryRequested(object sender, MemoryOperationEventArgs e)
        {
            if (workspace == null || workspace.Programmer == null) return;

            try
            {
                workspace.Programmer.Open();
                workspace.Programmer.ProgressChanged += OnProgrammerProgressChanged;
                workspace.Programmer.AsyncCompleted += OnWriteMemoryCompleted;

                progressView = new ProgressView();
                progressView.Title = "MCU Fusion";
                progressView.Cancelled += OnCancel;
                progressView.Text = "Writing memory...";
                progressView.UpdateProgress(0);

                stream = new MemoryStream(view.GetMemory(e.MemoryClass).ToArray());
                workspace.Programmer.WriteMemoryAsync(stream, e.MemoryClass);
                progressView.ShowDialog();
            }
            catch (Exception ex)
            {
                workspace.Programmer.Close();
                MessageBox.Show(ex.Message);
            }
        }

        private void OnWriteMemoryCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }

            workspace.Programmer.ProgressChanged -= OnProgrammerProgressChanged;
            workspace.Programmer.AsyncCompleted -= OnWriteMemoryCompleted;

            workspace.Programmer.Close();
            progressView.Close();
            stream.Dispose();
            stream = null;
        }

        private void OnProgrammerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressView.UpdateProgress(e.ProgressPercentage);
        }

        private void OnCancel(object sender, EventArgs e)
        {
            if (workspace == null || workspace.Programmer == null) return;

            workspace.Programmer.CancelAsync();
        }

        private void OnLoaded(object sender, EventArgs e)
        {
            if (arguments.Length > 0)
            {
                try
                {
                    OpenWorkspace(new FileInfo(arguments[0]));
                }
                catch (FileNotFoundException ex)
                {
                    view.ShowError(ex.Message);
                }
                catch
                {
                    view.ShowError(String.Format("Cannot open file \"{0}\".", arguments[0]));
                }
            }
        }

        private void OnClosing(object sender, CancelEventArgs e)
        {
            if (workspace != null && workspace.Modified)
            {
                bool? answer = view.ConfirmCloseWorkspace();
                switch (answer)
                {
                    case true:
                        SaveWorkspace();
                        break;
                    case null:
                        e.Cancel = true;
                        break;
                }
            }
        }

        private void OnExitRequested(object sender, EventArgs e)
        {
            view.Close();
        }

        private void OnGetCurrentWorkspaceRequested(object sender, DataTransferEventArgs<Workspace> e)
        {
            e.Data = workspace;
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            if (workspace != null)
            {
                workspace.Dispose();
            }
            if (progressView != null)
            {
                progressView.Dispose();
            }
        }

        #endregion

        #region Private Methods

        private void OpenWorkspace(FileInfo fileInfo)
        {
            if (workspace != null && workspace.Modified)
            {
                bool? answer = view.ConfirmCloseWorkspace();
                switch (answer)
                {
                    case true:
                        SaveWorkspace();
                        break;
                    case null:
                        return;
                }
            }

            FileInfo file = fileInfo ?? view.GetOpenWorkspaceName();
            if (file != null)
            {
                workspace = Workspace.Load(file);
                view.ShowWorkspace(workspace);
                AddRecentFile(file);
            }
        }

        private void SaveWorkspace()
        {
            if (workspace != null)
            {
                if (workspace.Path != null)
                {
                    workspace.Save(new FileInfo(workspace.Path));
                }
                else
                {
                    SaveWorkspaceAs();
                }
            }
        }

        private void SaveWorkspaceAs()
        {
            if (workspace != null)
            {
                FileInfo file = view.GetSaveWorkspaceName();
                if (file != null)
                {
                    workspace.Save(file);
                    AddRecentFile(file);
                }
            }
        }

        private void AddRecentFile(FileInfo file)
        {
            RecentFile rf = new RecentFile(file.FullName);
            Mutex mutex = GetRecentFilesMutex();
            try
            {
                mutex.WaitOne();
                RecentFilesList.Default.Reload();
                // TODO: FIX: It looks like Mono 2.4 does not call OnSettingsLoaded on reload.
                if (RecentFilesList.Default.Files == null)
                {
                    RecentFilesList.Default.Files = new RecentFilesCollection();
                }
                RecentFilesList.Default.Files.Add(rf);
                RecentFilesList.Default.Save();
                view.RefreshRecentFilesList(RecentFilesList.Default.Files);
            }
            finally
            {
                mutex.ReleaseMutex();
                mutex.Close();
            }
        }

        private static Mutex GetRecentFilesMutex()
        {
            return new Mutex(false, recentFilesMutexName);
        }

        #endregion

        #region Private Fields

        private readonly IMainView view;

        private IProgressView progressView;

        private Workspace workspace;

        private MemoryStream stream;

        private MemoryClassInfo memoryClass;

        private Object sync = new Object();

        private static readonly String recentFilesMutexName =
            String.Format(@"Global\{0}, version {1}",
                Application.ProductName,
                Application.ProductVersion);

        private readonly String[] arguments;

        private readonly ApplicationObject applicationObject;

        #endregion
    }
}
